"""A group of ``Shape`` instances."""

"""一组``Shape``实例。"""

from .Shape import Shape, Stroke, Fill, Hyperlink
from ..common.share import RectType
from ..common.Collection import Collection, ElementCollection
from ..common import share
from ..common import constants


class Shapes(ElementCollection):
    """A collection of ``Shape`` instances: ``Stroke`` or ``Fill``."""

    """ ``Shape``实例的集合：``Stroke``或``Fill``。"""

    def restore(self, raws: list):
        """Clean current instances and restore them from source dicts."""
        """清除当前实例并从源字典中恢复它们。"""
        self.reset()
        # Distinguish specified type by key like `start`, `end` and `uri`.
        # 通过诸如`start`、`end`和`uri`等键区分指定类型。
        for raw in raws:
            if "start" in raw:
                shape = Stroke(raw)
            elif "uri" in raw:
                shape = Hyperlink(raw)
            else:
                shape = Fill(raw)
            # add to list
            # 添加到列表
            self.append(shape)
        return self

    def _update_bbox(self, e: Shape):
        """override. Do nothing."""
        """ 重写。不做任何事情。"""

    @property
    def strokes(self):
        """Stroke Shapes, including table border, text underline and strike-through."""
        """ 描边形状，包括表格边框、文本下划线和删除线。"""
        instances = list(
            filter(lambda shape: isinstance(shape, Stroke), self._instances)
        )
        return Shapes(instances)

    @property
    def fillings(self):
        """Fill Shapes, including cell shading and highlight."""
        """ 填充形状，包括单元格底纹和高亮。"""
        # white bg-color is by default, so ignore those fillings
        # 白色背景色是默认的，所以忽略这些填充
        instances = list(
            filter(
                lambda shape: isinstance(shape, Fill)
                and shape.color != share.rgb_value((1, 1, 1)),
                self._instances,
            )
        )
        return Shapes(instances)

    @property
    def hyperlinks(self):
        """Hyperlink Shapes."""
        """ 超链接形状。"""
        instances = list(
            filter(lambda shape: isinstance(shape, Hyperlink), self._instances)
        )
        return Shapes(instances)

    @property
    def table_strokes(self):
        """Potential table borders."""
        """潜在的表格边框。"""
        instances = list(
            filter(
                lambda shape: shape.has_potential_type(RectType.BORDER), self._instances
            )
        )
        return ElementCollection(instances)

    @property
    def table_fillings(self):
        """Potential table shadings."""
        """潜在的表格底纹。"""
        instances = list(
            filter(
                lambda shape: shape.has_potential_type(RectType.SHADING),
                self._instances,
            )
        )
        return ElementCollection(instances)

    @property
    def text_style_shapes(self):
        """Potential text style based shapes,
        e.g. underline, strike-through, highlight and hyperlink."""
        """基于文本样式的潜在形状，
        例如下划线、删除线、高亮和超链接。"""

        def f(shape):
            return (
                shape.has_potential_type(RectType.HIGHLIGHT)
                or shape.has_potential_type(RectType.UNDERLINE)
                or shape.has_potential_type(RectType.STRIKE)
                or shape.has_potential_type(RectType.HYPERLINK)
            )

        instances = set(filter(f, self._instances))
        return ElementCollection(instances)

    def clean_up(self, max_border_width: float, shape_min_dimension: float):
        """Clean rectangles.
        清理矩形。

        * Delete shapes out of page.
        * 删除页面外的形状。
        * Delete small shapes (either width or height).
        * 删除小形状（宽度或高度）。
        * Merge shapes with same filling color.
        * 合并具有相同填充颜色的形状。
        * Detect semantic type.
        * 检测语义类型。

        Args:
            max_border_width (float): The max border width.
            max_border_width (float): 最大边框宽度。
            shape_min_dimension (float): Ignore shape if both width and height
                is lower than this value.
            shape_min_dimension (float): 如果宽度和高度都低于此值，则忽略形状。
        """
        if not self._instances:
            return

        # remove small shapes or shapes out of page; and
        # update bbox in case part of the shape is out of page
        # 删除小形状或页面外的形状；并且
        # 更新边界框，以防形状的一部分在页面外
        page_bbox = self.parent.bbox
        cleaned_shapes = []  # type: list[Shape]
        for s in self:
            if max(s.bbox.width, s.bbox.height) < shape_min_dimension:
                continue  # small shapes
            # 小形状
            bbox_in_page = s.bbox.intersect(page_bbox)
            if bbox_in_page.is_empty:
                continue  # shapes out of page
            # 页面外的形状
            cleaned_shapes.append(
                s.update_bbox(bbox_in_page)
            )  # ignore out of page part
            # 忽略页面外的部分

        # merge normal shapes if same filling color
        # 如果填充颜色相同，则合并普通形状
        merged_shapes = self._merge_shapes(cleaned_shapes)

        # convert Fill instance to Stroke if looks like stroke
        # 如果看起来像描边，则将Fill实例转换为Stroke
        shapes = []
        for shape in merged_shapes:
            if isinstance(shape, Fill):
                stroke = shape.to_stroke(max_border_width)
                shapes.append(stroke if stroke else shape)
            else:
                shapes.append(shape)
        self.reset(shapes)

        # detect semantic type
        # 检测语义类型
        self._parse_semantic_type()

    def assign_to_tables(self, tables: list):
        """Add Shape to associated cells of given tables.
        将Shape添加到给定表格的相关单元格。

        Args:
            tables (list): A list of TableBlock instances.
            tables (list): TableBlock实例的列表。
        """
        if not tables:
            return

        # assign shapes to table region
        # 将形状分配到表格区域
        shapes_in_tables = [[] for _ in tables]  # type: list[list[Shape]]
        shapes = []  # type: list[Shape]
        for shape in self._instances:
            # exclude explicit table borders which belongs to current layout
            # 排除属于当前布局的显式表格边框
            if shape.equal_to_type(RectType.BORDER) or shape.equal_to_type(
                RectType.SHADING
            ):
                shapes.append(shape)
                continue

            for table, shapes_in_table in zip(tables, shapes_in_tables):
                # fully contained in one table
                # 完全包含在一个表格中
                if table.bbox.contains(shape.bbox):
                    shapes_in_table.append(shape)
                    break

                # not possible in current table, then check next table
                # 在当前表格中不可能，然后检查下一个表格
                elif not table.bbox.intersects(shape.bbox):
                    continue

            # Now, this shape belongs to previous layout
            # 现在，这个形状属于前一个布局
            else:
                shapes.append(shape)

        # assign shapes to associated cells
        # 将形状分配给相关单元格
        for table, shapes_in_table in zip(tables, shapes_in_tables):
            # no contents for this table
            # 这个表格没有内容
            if not shapes_in_table:
                continue
            table.assign_shapes(shapes_in_table)

        self.reset(shapes)

    def plot(self, page):
        """Plot shapes for debug purpose.
        为调试目的绘制形状。
        Different colors are used to display the shapes in detected semantic types, e.g.
        yellow for text based shape (stroke, underline and highlight). Due to overlaps
        between Stroke and Fill related groups, some shapes are plot twice.
        使用不同的颜色来显示检测到的语义类型的形状，例如
        黄色用于基于文本的形状（描边、下划线和高亮）。由于Stroke和Fill相关组之间存在重叠，
        一些形状会被绘制两次。

        Args:
            page (fitz.Page): pdf page.
            page (fitz.Page): pdf页面。
        """
        # Table based shapes
        # 基于表格的形状
        # - table shading
        # - 表格底纹
        color = (152 / 255, 251 / 255, 152 / 255)
        for shape in self.table_fillings:
            shape.plot(page, color)

        # - table borders
        # - 表格边框
        color = (0, 0, 0)
        for shape in self.table_strokes:
            shape.plot(page, color)

        # Text based shapes
        # 基于文本的形状
        # - underline
        # - 下划线
        # - strike-through
        # - 删除线
        # - highlight
        # - 高亮
        # - hyperlink
        # - 超链接
        color = (1, 1, 0)
        for shape in self.text_style_shapes:
            shape.plot(page, color)

    @staticmethod
    def _merge_shapes(shapes):
        """Merge shapes if same filling color. Note the merged bbox must match source shapes
        as more as possible."""
        """如果填充颜色相同，则合并形状。注意合并后的边界框必须尽可能地匹配源形状。"""
        # shapes excluding hyperlink first
        # 首先排除超链接的形状
        normal_shapes = list(filter(lambda shape: not shape.is_determined, shapes))

        # group by color and connectivity (with margin considered)
        # 按颜色和连通性（考虑边距）分组
        def f(a, b):
            return a.color == b.color and a.bbox.intersects(
                b.get_expand_bbox(constants.TINY_DIST)
            )

        groups = Collection(normal_shapes).group(f)

        merged_shapes = []
        for group in groups:
            merged_area = group.bbox.get_area()
            sum_area = sum(shape.bbox.get_area() for shape in group)
            if sum_area / merged_area >= constants.FACTOR_ALMOST:
                merged_shapes.append(group[0].update_bbox(group.bbox))
            else:
                merged_shapes.extend(group)

        # add hyperlinks back
        # 添加回超链接
        hyperlinks = filter(
            lambda shape: shape.equal_to_type(RectType.HYPERLINK), shapes
        )
        merged_shapes.extend(hyperlinks)
        return merged_shapes

    def _parse_semantic_type(self):
        """Detect shape type based on the position to text blocks.
        根据与文本块的位置关系检测形状类型。

        .. note::
            Stroke shapes are grouped on connectivity to each other, but in some cases,
            the gap between borders and underlines/strikes are very close, which leads
            to an incorrect table structure. So, it's required to distinguish them in
            advance, though we needn't to ensure 100% accuracy. They are finally determined
            when parsing table structure and text format.
            Stroke形状根据彼此的连通性进行分组，但在某些情况下，
            边框和下划线/删除线之间的间隙非常接近，这导致
            表格结构不正确。因此，需要提前区分它们，
            尽管我们不需要确保100%的准确性。它们最终在
            解析表格结构和文本格式时确定。
        """
        # blocks in page (the original blocks without any further processing)
        # 页面中的块（未进行任何进一步处理的原始块）
        blocks = self._parent.blocks
        blocks.sort_in_reading_order()

        # check positions between shapes and text blocks
        # 检查形状和文本块之间的位置
        for shape in self._instances:
            shape.parse_semantic_type(blocks)
